﻿using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;

namespace EncryptHelpers
{
    public static class EncryptHelper
    {
        public static byte[] GetBytesRegardlessOfEncoding(string text)
        {
            if (null == text) throw new ArgumentNullException("text");

            var bin = new BinaryFormatter();
            using (var ms = new MemoryStream())
            {
                bin.Serialize(ms, text);
                ms.Seek(0, SeekOrigin.Begin);
                return ms.ToArray();
            }
        }

        public static string GetStringRegardlessOfEncoding(byte[] bytes)
        {
            if (null == bytes) throw new ArgumentNullException("bytes");

            var bin = new BinaryFormatter();
            using (var ms = new MemoryStream())
            {
                ms.Write(bytes, 0, bytes.Length);
                ms.Seek(0, SeekOrigin.Begin);
                return bin.Deserialize(ms) as string;
            }
        }

        public static string ToBase64(string textToEncrypt)
        {
            var inputBytes = GetBytesRegardlessOfEncoding(textToEncrypt);
            return Convert.ToBase64String(inputBytes, Base64FormattingOptions.None);
        }

        public static string FromBase64(string base64String)
        {
            var bytes = Convert.FromBase64String(base64String);
            return GetStringRegardlessOfEncoding(bytes);
        }

        public static byte[] BitStringToBytes(string bitString)
        {
            if (null == bitString) throw new ArgumentNullException("bitString");

            return bitString.Split(new char[] { '-' }, StringSplitOptions.None)
                .Select(it => byte.Parse(it, NumberStyles.HexNumber))
                .ToArray();
        }

        public static class RSAHelper
        {
            public static RSACryptoServiceProvider CreateRSACSP(int dwKeySize, out string publicKey, out string privateKey)
            {
                var rsa = new RSACryptoServiceProvider(dwKeySize);
                publicKey = rsa.ToXmlString(false);
                privateKey = rsa.ToXmlString(true);

                return rsa;
            }

            /// <summary>
            /// Computes the hash value of a subset of the specified byte array using the
            /// <para>specified hash algorithm, and signs the resulting hash value.</para>
            /// Returns base64 string of the signature.
            /// </summary>
            /// <param name="rsa">The RSA Crypto Service Provider.</param>
            /// <param name="textToSign">The text to sign.</param>
            /// <param name="hashAlgorithm">The hash algorithm.</param>
            /// <returns></returns>
            public static string SignData(RSACryptoServiceProvider rsa, string textToSign, HashAlgorithm hashAlgorithm)
            {
                var dataToSign = GetBytesRegardlessOfEncoding(textToSign);
                var signature = rsa.SignData(dataToSign, 0, dataToSign.Length, hashAlgorithm);
                return Convert.ToBase64String(signature, Base64FormattingOptions.None);
            }

            /// <summary>
            /// Computes the hash value of a subset of the specified byte array using the
            /// <para>specified hash algorithm, and signs the resulting hash value.</para>
            /// </summary>
            /// <param name="rsa">The RSA Crypto Service Provider.</param>
            /// <param name="data">The data that was signed.</param>
            /// <param name="signature">The signature data to be verified(base64 string).</param>
            /// <param name="hashAlgorithm">The name of the hash algorithm used to create the hash value of the data.</param>
            /// <returns></returns>
            public static bool VerifyData(RSACryptoServiceProvider rsa, string text, string signature, HashAlgorithm hashAlgorithm)
            {
                var dataBytes = GetBytesRegardlessOfEncoding(text);
                var sigBytes = Convert.FromBase64String(signature);
                return rsa.VerifyData(dataBytes, hashAlgorithm, sigBytes);
            }

            /// <summary>
            /// Encrypt text using RSA Crypto Service Provider with the public key. 
            /// </summary>
            /// <param name="textToEncrypt">The text to encrypt.</param>
            /// <param name="rsa">The RSA Crypto Service Provider used to encrypt.</param>
            /// <returns></returns>
            public static string Encrypt(RSACryptoServiceProvider rsa, string textToEncrypt)
            {
                var inputBytes = GetBytesRegardlessOfEncoding(textToEncrypt);
                var encryptedBytes = rsa.Encrypt(inputBytes, false);
                return BitConverter.ToString(encryptedBytes);
            }

            /// <summary>
            /// Decrypt using RSA Crypto Service Provider.
            /// </summary>
            /// <param name="encryptedText">The encrypted text, bit string, as in "AA-BB-CC-00-11-22".</param>
            /// <param name="key"></param>
            /// <returns></returns>
            public static string Decrypt(RSACryptoServiceProvider rsa, string encryptedText)
            {
                var encryptedBytes = BitStringToBytes(encryptedText);
                var decryptedBytes = rsa.Decrypt(encryptedBytes, false);
                return GetStringRegardlessOfEncoding(decryptedBytes);
            }
        }

        public static class TripleDESHelper
        {
            public static TripleDESCryptoServiceProvider CreateTripleDESCSP()
            {
                var des3 = new TripleDESCryptoServiceProvider();
                des3.Mode = CipherMode.ECB;
                des3.Padding = PaddingMode.PKCS7;

                return des3;
            }

            /// <summary>
            /// Make the Key and IV for triple DES encription.
            /// </summary>
            /// <param name="des">The DES Crypto Service Provider.</param>
            /// <param name="key">Output base64 string of generated Key.</param>
            /// <param name="iv">Output base64 string of generated IV.</param>
            public static void MakeKey(out string key, out string iv)
            {
                var des3 = CreateTripleDESCSP();
                des3.GenerateIV();
                des3.GenerateKey();

                key = Convert.ToBase64String(des3.Key);
                iv = Convert.ToBase64String(des3.IV);
            }

            public static string Encrypt(string textToEncrypt, string key, string iv)
            {
                var des3 = CreateTripleDESCSP();

                des3.Key = Convert.FromBase64String(key);
                des3.IV = Convert.FromBase64String(iv);

                var encryptor = des3.CreateEncryptor();

                var inputBytes = GetBytesRegardlessOfEncoding(textToEncrypt);
                var encryptedBytes = encryptor.TransformFinalBlock(inputBytes, 0, inputBytes.Length);
                return BitConverter.ToString(encryptedBytes);
            }

            public static string Decrypt(string encryptedText, string key, string iv)
            {
                var des3 = CreateTripleDESCSP();

                des3.Key = Convert.FromBase64String(key);
                des3.IV = Convert.FromBase64String(iv);

                var decryptor = des3.CreateDecryptor();

                var encryptedBytes = BitStringToBytes(encryptedText);
                var decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
                return GetStringRegardlessOfEncoding(decryptedBytes);
            }
        }

        public static class DESHelper
        {
            public static DESCryptoServiceProvider CreateDESCSP()
            {
                var des = new DESCryptoServiceProvider();
                des.Mode = CipherMode.ECB;
                des.Padding = PaddingMode.PKCS7;

                return des;
            }

            /// <summary>
            /// Make the Key and IV for triple DES encription.
            /// </summary>
            /// <param name="des">The DES Crypto Service Provider.</param>
            /// <param name="key">Output base64 string of generated Key.</param>
            /// <param name="iv">Output base64 string of generated IV.</param>
            public static void MakeKey(out string key, out string iv)
            {
                var des = CreateDESCSP();

                des.GenerateIV();
                des.GenerateKey();

                key = Convert.ToBase64String(des.Key);
                iv = Convert.ToBase64String(des.IV);
            }

            public static string Encrypt(string textToEncrypt, string key, string iv)
            {
                var des = CreateDESCSP();

                des.Key = Convert.FromBase64String(key);
                des.IV = Convert.FromBase64String(iv);

                var encryptor = des.CreateEncryptor();

                var inputBytes = GetBytesRegardlessOfEncoding(textToEncrypt);
                var encryptedBytes = encryptor.TransformFinalBlock(inputBytes, 0, inputBytes.Length);
                return BitConverter.ToString(encryptedBytes);
            }

            public static string Decrypt(string encryptedText, string key, string iv)
            {
                var des = CreateDESCSP();

                des.Key = Convert.FromBase64String(key);
                des.IV = Convert.FromBase64String(iv);

                var decryptor = des.CreateDecryptor();

                var encryptedBytes = BitStringToBytes(encryptedText);
                var decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
                return GetStringRegardlessOfEncoding(decryptedBytes);
            }
        }
    }
}
